package org.xfg.domain.strategy.service.armory;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.xfg.domain.strategy.model.entity.StrategyAwardEntity;
import org.xfg.domain.strategy.model.entity.StrategyEntity;
import org.xfg.domain.strategy.model.entity.StrategyRuleEntity;
import org.xfg.domain.strategy.repository.IStrategyRepository;
import org.xfg.types.common.Constants;
import org.xfg.types.enums.ResponseCode;
import org.xfg.types.exception.AppException;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.security.SecureRandom;
import java.util.*;

@Slf4j
@Service
public class StrategyArmory implements IStrategyArmory,IStrategyDispatch{

    @Resource
    private IStrategyRepository repository;

    @Override
    public boolean assembleLotteryStrategyByActivityId(Long activityId) {
        Long strategyId = repository.queryStrategyIdByActivityId(activityId);
        return assembleLotteryStrategy(String.valueOf(strategyId));
    }

    @Override
    public boolean assembleLotteryStrategy(String strategyId) {
        //1. 通过策略id查询该策略下的奖品
        List<StrategyAwardEntity> strategyAwardEntities = repository.queryStrategyAwardList(strategyId);
        // 2. 缓存奖品库存(用于decr扣减库存使用)
        for (StrategyAwardEntity strategyAward:strategyAwardEntities){
            Integer awardId = strategyAward.getAwardId();
            Integer awardCount = strategyAward.getAwardCount();
            cacheStrategyAwardCount(Long.parseLong(strategyId),awardId,awardCount);
        }

        // 3 默认装配
        assembleLotteryStrategy(strategyId,strategyAwardEntities);// 默认装配

        //4 权重配置 适用于 rule_weight 权重规则配置
        StrategyEntity strategyEntity = repository.queryStrategyEntityByStrategyId(strategyId);
        if (null == strategyEntity || null == strategyEntity.getRuleModels() || null == strategyEntity.getRuleWeight()) return true; //没有权重规则配置则正常装配奖品对象
        String ruleWeight = strategyEntity.getRuleWeight();


        // 5. 权重装配
        StrategyRuleEntity strategyRuleEntity = repository.queryStrategyRule(strategyId, ruleWeight);
        // 存在策略配置不存在具体规则的情况下抛出异常
        if (null == strategyRuleEntity){
            throw new AppException(ResponseCode.STRATEGY_RULE_WEIGHT_IS_NULL.getCode(), ResponseCode.STRATEGY_RULE_WEIGHT_IS_NULL.getInfo());
        }
        // map数据结构 4000 -> 102,103,104
        Map<String, List<Integer>> ruleWeightValueMap = strategyRuleEntity.getRuleWeightValues();
        Set<String> keys = ruleWeightValueMap.keySet();
        for (String key : keys) {
            // 奖品id列表
            List<Integer> ruleWeightValues = ruleWeightValueMap.get(key);
            // 深拷贝所有奖品列表
            ArrayList<StrategyAwardEntity> strategyAwardEntitiesClone = new ArrayList<>(strategyAwardEntities);
            // 只保留策略配置的奖品列表
            strategyAwardEntitiesClone.removeIf(item->!ruleWeightValues.contains(item.getAwardId()));
            assembleLotteryStrategy(strategyId.concat("_").concat(key),strategyAwardEntitiesClone);//只装配配置的奖品表
        }

        return true;
    }


    /**
     * 缓存奖品库存到Redis
     */
    private void cacheStrategyAwardCount(Long strategyId, Integer awardId, Integer awardCount) {
        String cacheKey = Constants.RedisKey.STRATEGY_AWARD_COUNT_KEY + strategyId + Constants.UNDERLINE + awardId;
        repository.cacheStrategyAwardCount(cacheKey, awardCount);
    }

    /**
     * 装配奖品的通用方法
     * @param strategyId: 抽奖策略id
     * @param strategyAwardEntities: 待装配的奖品
     */
    public void assembleLotteryStrategy(String strategyId, List<StrategyAwardEntity> strategyAwardEntities) {
        //1. 取中奖最小的概率
        BigDecimal minAwardRate = strategyAwardEntities.stream()
                .map(StrategyAwardEntity::getAwardRate) // 取出集合对象的 awardRate 属性重新组成一个集合
                .min(BigDecimal::compareTo) //使用BigDecimal的compareTo比较大小取最小的一个
                .orElse(BigDecimal.ZERO); // 结果为空时取 0
        //2.获取概率总和
        BigDecimal totalAwardRate = strategyAwardEntities.stream()
                .map(StrategyAwardEntity::getAwardRate)
                .reduce(BigDecimal.ZERO, BigDecimal::add);//累加,第一个参数是累加之后再加几
        // 3. 用 1 % 0.0001 获得概率范围，百分位、千分位、万分位
        // 解释：3.14 CEILING 后的值：4（最接近但不大于3.14的整数）负数向0取整：原始值：-2.75 CEILING后的值：-2
        // （向0方向舍入，结果保持不变）
        BigDecimal rateRange = totalAwardRate.divide(minAwardRate, 0, RoundingMode.CEILING);

        // 4. 生成策略奖品概率查找表「这里指需要在list集合中，存放上对应的奖品占位即可，占位越多等于概率越高」
        List<Integer> strategyAwardSearchRateTables = new ArrayList<>(rateRange.intValue());
        for (StrategyAwardEntity strategyAward:strategyAwardEntities) {
            Integer awardId = strategyAward.getAwardId();//抽奖奖品ID
            BigDecimal awardRate = strategyAward.getAwardRate();//奖品对应的中奖概率
            // 计算出每个概率值需要存放到查找表的数量，循环填充, 向上取整概率存放的格子保证够用
            // setScale函数的0表示取0位小数, CEILING表示向上取整
            for (int i = 0; i < rateRange.multiply(awardRate).setScale(0,RoundingMode.CEILING).intValue(); i++) {
                strategyAwardSearchRateTables.add(awardId);
            }
        }
        //5. 对存储的奖品进行乱序
        Collections.shuffle(strategyAwardSearchRateTables);

        // 6. 生成出Map集合，key值是一个序号,他对应的是奖品id。
        Map<Integer, Integer> shuffleStrategyAwardSearchRateTable = new LinkedHashMap<>();
        for (int i = 0; i < strategyAwardSearchRateTables.size(); i++) {
            shuffleStrategyAwardSearchRateTable.put(i,strategyAwardSearchRateTables.get(i));
        }

        //7. 存储进redis
        repository.storeStrategyAwardSearchRateTable(strategyId,shuffleStrategyAwardSearchRateTable.size(),shuffleStrategyAwardSearchRateTable);

    }




    @Override
    public Integer getRandomAwardId(Long strategyId) {
        // 分布式部署下，不一定为当前应用做的策略装配。也就是值不一定会保存到本应用，而是分布式应用，所以需要从 Redis 中获取。
        int rateRange = repository.getRateRange(String.valueOf(strategyId));
        // 通过生成的随机值，获取概率值奖品查找表的结果
        return repository.getStrategyAwardAssemble(String.valueOf(strategyId), new SecureRandom().nextInt(rateRange));
    }

    @Override
    public Integer getRandomAwardId(Long strategyId, String ruleWeightValue) {
        String key = String.valueOf(strategyId).concat("_").concat(ruleWeightValue);
        // 分布式部署下，不一定为当前应用做的策略装配。也就是值不一定会保存到本应用，而是分布式应用，所以需要从 Redis 中获取。
        int rateRange = repository.getRateRange(key);
        // 通过生成的随机值，获取概率值奖品查找表的结果
        return repository.getStrategyAwardAssemble(key, new SecureRandom().nextInt(rateRange));
    }

    @Override
    public Boolean subtractionAwardStock(Long strategyId, Integer awardId, Date endDateTime) {
        String cacheKey = Constants.RedisKey.STRATEGY_AWARD_COUNT_KEY + strategyId + Constants.UNDERLINE + awardId;
        return repository.subtractionAwardStock(cacheKey,endDateTime);
    }

}























